﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Renci.iRods.Packets;
using Renci.iRods.Common;
using System.Net.Sockets;
using System.Diagnostics;
using System.Security.Cryptography;
using Renci.iRods.Exceptions;
using System.Net;
using System.Threading;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.Collections.ObjectModel;
using System.Linq.Expressions;
using Renci.iRods.Data;
using Renci.iRods.Linq;

namespace Renci.iRods
{
    public class IRodsConnection : IDisposable
    {
        private const int _maximumPasswordLength = 50;

        private Socket _socket;

        private string _workingDirectory;

        private PackingType _packingType = PackingType.Xml;

        private IRodsQueryProvider _linqProvider;

        /// <summary>
        /// Holds session challenge bytes
        /// </summary>
        private byte[] _sessionChallenge;

        public string HomeDirectory { get; private set; }

        public string WorkingDirectory
        {
            get
            {
                return this._workingDirectory;
            }
            set
            {
                var path = this.ParsePath(value);
                var state = this.GetObjectStatus(path);
                if (state != null)
                {
                    this._workingDirectory = value;
                }
                else
                {
                    throw new InvalidOperationException(string.Format("Path '{0}' is not valid.", path));
                }
            }
        }

        public string ResourceName { get; private set; }

        public bool IsCatEnabled { get; private set; }

        public DateTime ServerBootTime { get; private set; }

        public string ReleaseVersion { get; private set; }

        public string ApiVersion { get; private set; }

        public string RodsZone { get; private set; }

        private string _localZoneName;
        /// <summary>
        /// Gets the name of the local zone.
        /// </summary>
        /// <value>
        /// The name of the local zone.
        /// </value>
        public string LocalZoneName
        {
            get
            {
                if (string.IsNullOrEmpty(this._localZoneName))
                {
                    this._localZoneName = this.ExecuteSimpleQuery(1, "select zone_name from R_ZONE_MAIN where zone_type_name=?", "local").Trim();
                }

                return this._localZoneName;
            }
        }

        public bool IsConnected { get; private set; }

        public ConnectionInfo ConnectionInfo { get; private set; }

        public IRodsConnection(ConnectionInfo account)
        {
            this.ConnectionInfo = account;
            this._linqProvider = new IRodsQueryProvider(this);
            this.Collections = new UpdatableTable<IRodsCollection>(this._linqProvider, "Collections");
            this.Files = new UpdatableTable<IRodsFile>(this._linqProvider, "Files");
            this.ObjectAccesses = new UpdatableTable<ObjectAccess>(this._linqProvider, "ObjectAccesses");
            this.ObjectAudits = new UpdatableTable<ObjectAudit>(this._linqProvider, "ObjectAudits");
            this.Quotas = new UpdatableTable<Quota>(this._linqProvider, "Quotas");
            this.QuotaUsages = new UpdatableTable<QuotaUsage>(this._linqProvider, "QuotaUsages");
            this.Resources = new UpdatableTable<Resource>(this._linqProvider, "Resources");
            this.ResourceGroups = new UpdatableTable<ResourceGroup>(this._linqProvider, "ResourceGroups");
            this.Rules = new UpdatableTable<Rule>(this._linqProvider, "Rules");
            this.RuleDataVariableMaps = new UpdatableTable<RuleDataVariableMap>(this._linqProvider, "RuleDataVariableMaps");
            this.RuleExecutions = new UpdatableTable<RuleExecution>(this._linqProvider, "RuleExecutions");
            this.RuleFunctionNameMaps = new UpdatableTable<RuleFunctionNameMap>(this._linqProvider, "RuleFunctionNameMaps");
            this.Servers = new UpdatableTable<Server>(this._linqProvider, "Servers");
            this.ServerDigests = new UpdatableTable<ServerDigest>(this._linqProvider, "ServerDigests");
            this.Tokens = new UpdatableTable<Token>(this._linqProvider, "Tokens");
            this.Users = new UpdatableTable<User>(this._linqProvider, "Users");
            this.UserGroups = new UpdatableTable<UserGroup>(this._linqProvider, "UserGroups");
            this.Zones = new UpdatableTable<Zone>(this._linqProvider, "Zones");
        }

        public UpdatableTable<IRodsFile> Files { get; set; }

        public UpdatableTable<IRodsCollection> Collections { get; set; }

        public UpdatableTable<ObjectAccess> ObjectAccesses { get; set; }

        public UpdatableTable<ObjectAudit> ObjectAudits { get; set; }

        public UpdatableTable<Quota> Quotas { get; set; }

        public UpdatableTable<QuotaUsage> QuotaUsages { get; set; }

        public UpdatableTable<Resource> Resources { get; set; }

        public UpdatableTable<ResourceGroup> ResourceGroups { get; set; }

        public UpdatableTable<Rule> Rules { get; set; }

        public UpdatableTable<RuleDataVariableMap> RuleDataVariableMaps { get; set; }

        public UpdatableTable<RuleExecution> RuleExecutions { get; set; }

        public UpdatableTable<RuleFunctionNameMap> RuleFunctionNameMaps { get; set; }

        public UpdatableTable<Server> Servers { get; set; }

        public UpdatableTable<ServerDigest> ServerDigests { get; set; }

        public UpdatableTable<Token> Tokens { get; set; }

        public UpdatableTable<User> Users { get; set; }

        public UpdatableTable<UserGroup> UserGroups { get; set; }

        public UpdatableTable<Zone> Zones { get; set; }

        public void Open()
        {
            var startupPacket = new StartupPacket
            {
                PackingType = this._packingType,
                ReconnectFlag = 0,
                ConnectCounter = 0,
                ProxyUsername = this.ConnectionInfo.Username,
                ProxyRcatZone = this.ConnectionInfo.Zone,
                ClientUsername = this.ConnectionInfo.Username,
                ClientRcatZone = this.ConnectionInfo.Zone,
                RelVersion = this.ConnectionInfo.Version,
                ApiVersion = this.ConnectionInfo.ApiVersion,
                Option = this.ConnectionInfo.Option,
            };

            var ep = new IPEndPoint(Dns.GetHostAddresses(this.ConnectionInfo.Host)[0], (int)this.ConnectionInfo.Port);
            this._socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

            
            this._socket.Connect(ep);

            //  Send startup message
            this.SendMessage(new Message(MessageTypes.Connect, RequestCodes.None, startupPacket));

            //  Receive version message
            var version = this.ReceiveMessage<VersionPacket>();

            if (version.Exception != null)
            {
                throw version.Exception;
            }

            //  TODO:   Handle received version message

            //  If username is public no authentication is needed
            if (!this.ConnectionInfo.Username.Equals("public", StringComparison.InvariantCultureIgnoreCase))
            {
                switch (this.ConnectionInfo.AuthenticationScheme)
                {
                    case AuthenticationTypes.Password:
                        var request = this.RequestAuthenticationRequest();
                        this._sessionChallenge = request.Challenge;
                        this.RequestAuthenticationResponse(request.Challenge);
                        break;
                    case AuthenticationTypes.Kerberos:
                        throw new NotImplementedException();
                    case AuthenticationTypes.Gsi:
                        throw new NotImplementedException();
                    default:
                        break;
                }
            }

            //  Get server information
            this.SendMessage(new Message(MessageTypes.Request, RequestCodes.GET_MISC_SVR_INFO_AN));

            var response = this.ReceiveMessage<ServerInformationPacket>();

            if (response.Exception != null)
            {
                throw response.Exception;
            }

            this.IsCatEnabled = response.Body.ServerType > 1;
            this.ServerBootTime = response.Body.ServerBootTime;
            this.ReleaseVersion = response.Body.ReleaseVersion;
            this.ApiVersion = response.Body.ApiVersion;
            this.RodsZone = response.Body.RodsZone;
            this._workingDirectory = this.HomeDirectory = string.Format("/{0}/home/{1}", this.RodsZone, this.ConnectionInfo.Username);
            //  TODO:   Calculate default resource if empty
            this.ResourceName = this.ConnectionInfo.DefaultResource;

            //  TODO:   Refactor it to include socket status
            this.IsConnected = true;
        }

        public void Close()
        {
            //  TODO:   Clean all the properties that are set during "Open".
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        public IRodsDataReader ExecuteQuery(IRodsQuery query)
        {
            return new IRodsDataReader(this, query);
        }

        #region Administrative functions

        /// <summary>
        /// Adds new user.
        /// </summary>
        /// <param name="username">The username.</param>
        /// <param name="userType">Type of the user.</param>
        public void AddUser(string username, string userType)
        {
            var userInfo = username.Split(new char[] { '#' });

            var irodsUserName = userInfo[0];
            var irodsZoneName = string.Empty;

            if (userInfo.Length > 1)
            {
                irodsZoneName = userInfo[1];

                if (irodsZoneName == this.LocalZoneName)
                {
                    irodsZoneName = string.Empty;
                }
            }

            //  Validate that username is valid
            if (irodsUserName.IndexOf('@') == irodsUserName.LastIndexOf('@'))
            {
                this.ExecuteGeneralAdmin("add", "user", irodsUserName, userType, irodsZoneName);
            }
            else
            {
                throw new IRodsException("Invalid user name format");
            }

        }

        /// <summary>
        /// Modifies the user.
        /// </summary>
        /// <param name="username">The username to modify.</param>
        /// <param name="propertyName">Name of the property to modify.</param>
        /// <param name="value">The property value.</param>
        public void ModifyUser(string username, string propertyName, string value)
        {
            var validProperties = new string[] { "password", "type", "zone", "comment", "info" };

            if (validProperties.Contains(propertyName))
            {
                if (propertyName.Equals("password", StringComparison.InvariantCultureIgnoreCase))
                {
                    //throw new NotSupportedException("Password change currently not supported.");

                    value = EncryptPassword(value);
                }

                this.ExecuteGeneralAdmin("modify", "user", username, propertyName, value);
            }
            else
            {
                throw new IRodsException(string.Format("Property '{0}' is not valid.", propertyName));
            }
        }

        /// <summary>
        /// Adds the user authentication.
        /// </summary>
        /// <param name="username">The username.</param>
        /// <param name="authenticationName">User authentication name to add.</param>
        public void AddUserAuthentication(string username, string authenticationName)
        {
            this.ExecuteGeneralAdmin("modify", "user", username, "addAuth", authenticationName);
        }

        /// <summary>
        /// Removes the user authentication.
        /// </summary>
        /// <param name="username">The username.</param>
        /// <param name="authenticationName">User authentication name to remove.</param>
        public void RemoveUserAuthentication(string username, string authenticationName)
        {
            this.ExecuteGeneralAdmin("modify", "user", username, "rmAuth", authenticationName);
        }

        /// <summary>
        /// Removes the user.
        /// </summary>
        /// <param name="username">The username to remove.</param>
        public void RemoveUser(string username)
        {
            //  TODO:   Check for valid username format
            this.ExecuteGeneralAdmin("rm", "user", username);
        }

        /// <summary>
        /// Adds new collection.
        /// </summary>
        /// <param name="directoryName">Name of the new collection.</param>
        public void AddDir(string directoryName)
        {
            this.AddDir(directoryName, null);
        }

        /// <summary>
        /// Adds new collection.
        /// </summary>
        /// <param name="directoryName">Name of the collection.</param>
        /// <param name="username">The username the collection will belong to.</param>
        public void AddDir(string directoryName, string username)
        {
            this.ExecuteGeneralAdmin("add", "dir", directoryName, username);
        }

        /// <summary>
        /// Removes the collection.
        /// </summary>
        /// <param name="directoryName">Name of the collection to remove.</param>
        public void RemoveDir(string directoryName)
        {
            this.ExecuteGeneralAdmin("rm", "dir", directoryName);
        }

        /// <summary>
        /// Adds new resource.
        /// </summary>
        /// <param name="name">The name of the new resource.</param>
        /// <param name="typeName">Name of resource type.</param>
        /// <param name="className">Name of resource class.</param>
        /// <param name="host">The resource host name.</param>
        /// <param name="path">The resource path.</param>
        public void AddResource(string name, string typeName, string className, string host, string path)
        {
            this.ExecuteGeneralAdmin("add", "resource", name, typeName, className, host, path);
        }

        /// <summary>
        /// Modifies the resource.
        /// </summary>
        /// <param name="name">The name of the resource to modify.</param>
        /// <param name="propertyName">Name of the property to modify.</param>
        /// <param name="value">The property value.</param>
        public void ModifyResource(string name, string propertyName, string value)
        {
            var validProperties = new string[] { "name", "type", "class", "host", "path", "status", "comment", "info", "freespace" };

            if (validProperties.Contains(propertyName))
            {
                this.ExecuteGeneralAdmin("modify", "resource", name, propertyName, value);
            }
            else
            {
                throw new IRodsException(string.Format("Property '{0}' is not valid.", propertyName));
            }
        }

        /// <summary>
        /// Removes the resource.
        /// </summary>
        /// <param name="name">The name of the resource to remove.</param>
        public void RemoveResource(string name)
        {
            this.ExecuteGeneralAdmin("rm", "resource", name);
        }

        /// <summary>
        /// Adds new zone.
        /// </summary>
        /// <param name="name">The name of the new zone.</param>
        /// <param name="type">The type name of the new zone.</param>
        /// <param name="connectionInfo">The connection information of the new zone.</param>
        /// <param name="comment">The comment of the new zone.</param>
        public void AddZone(string name, string type, string connectionInfo, string comment)
        {
            this.ExecuteGeneralAdmin("add", "zone", name, type, connectionInfo, comment);
        }

        /// <summary>
        /// Modifies the zone.
        /// </summary>
        /// <param name="name">The name of the zone to modify.</param>
        /// <param name="propertyName">Name of the property to modify.</param>
        /// <param name="value">The property value.</param>
        public void ModifyZone(string name, string propertyName, string value)
        {
            var validProperties = new string[] { "name", "conn", "comment" };

            if (validProperties.Contains(propertyName))
            {
                this.ExecuteGeneralAdmin("modify", "zone", name, propertyName, value);
            }
            else
            {
                throw new IRodsException(string.Format("Property '{0}' is not valid.", propertyName));
            }
        }

        /// <summary>
        /// Removes the zone.
        /// </summary>
        /// <param name="name">The name of the zone to remove.</param>
        public void RemoveZone(string name)
        {
            this.ExecuteGeneralAdmin("rm", "zone", name);
        }

        /// <summary>
        /// Adds new group.
        /// </summary>
        /// <param name="name">The name of the new group.</param>
        public void AddGroup(string name)
        {
            this.ExecuteGeneralAdmin("add", "user", name, "rodsgroup", this.RodsZone);
        }

        /// <summary>
        /// Removes the group.
        /// </summary>
        /// <param name="name">The name of the group to remove.</param>
        public void RemoveGroup(string name)
        {
            this.ExecuteGeneralAdmin("rm", "user", name, this.RodsZone);
        }

        /// <summary>
        /// Adds the user to group.
        /// </summary>
        /// <param name="groupName">Name of the group.</param>
        /// <param name="username">The username to add to the group.</param>
        public void AddUserToGroup(string groupName, string username)
        {
            this.ExecuteGeneralAdmin("modify", "group", groupName, "add", username);
        }

        /// <summary>
        /// Removes the user from group.
        /// </summary>
        /// <param name="groupName">Name of the group.</param>
        /// <param name="username">The username to remove from the group.</param>
        public void RemoveUserFromGroup(string groupName, string username)
        {
            this.ExecuteGeneralAdmin("modify", "group", groupName, "remove", username);
        }

        /// <summary>
        /// Adds the resource to group.
        /// </summary>
        /// <param name="groupName">Name of the group.</param>
        /// <param name="resourceName">Name of the resource to remove from the group.</param>
        public void AddResourceToGroup(string groupName, string resourceName)
        {
            this.ExecuteGeneralAdmin("modify", "resourcegroup", groupName, "add", resourceName);
        }

        /// <summary>
        /// Removes the resource from group.
        /// </summary>
        /// <param name="groupName">Name of the group.</param>
        /// <param name="resourceName">Name of the resource to remove from the group.</param>
        public void RemoveResourceFromGroup(string groupName, string resourceName)
        {
            this.ExecuteGeneralAdmin("modify", "resourcegroup", groupName, "remove", resourceName);
        }

        /// <summary>
        /// Adds new token.
        /// </summary>
        /// <param name="tokenNamespace">The token namespace.</param>
        /// <param name="name">The name of the token.</param>
        /// <param name="value">The value of the token.</param>
        public void AddToken(string tokenNamespace, string name, string value)
        {
            this.AddToken(tokenNamespace, name, value, null, null);
        }

        /// <summary>
        /// Adds new token.
        /// </summary>
        /// <param name="tokenNamespace">The token namespace.</param>
        /// <param name="name">The name of the token.</param>
        /// <param name="value1">The value of the token.</param>
        /// <param name="value2">Second value of the token.</param>
        public void AddToken(string tokenNamespace, string name, string value1, string value2)
        {
            this.AddToken(tokenNamespace, name, value1, value2, null);
        }

        /// <summary>
        /// Adds new token.
        /// </summary>
        /// <param name="tokenNamespace">The token namespace.</param>
        /// <param name="name">The name of the token.</param>
        /// <param name="value1">The value of the token.</param>
        /// <param name="value2">Second value of the token.</param>
        /// <param name="value3">Third value of the token.</param>
        public void AddToken(string tokenNamespace, string name, string value1, string value2, string value3)
        {
            this.ExecuteGeneralAdmin("add", "token", tokenNamespace, name, value1, value2, value3);
        }

        /// <summary>
        /// Removes the token.
        /// </summary>
        /// <param name="tokenNamespace">The token namespace.</param>
        /// <param name="name">The name of the token to remove.</param>
        public void RemoveToken(string tokenNamespace, string name)
        {
            this.RemoveToken(tokenNamespace, name, null);
        }

        /// <summary>
        /// Removes the token.
        /// </summary>
        /// <param name="tokenNamespace">The token namespace.</param>
        /// <param name="name">The name of the token to remove.</param>
        /// <param name="value">The value of the token. (Currently ignored).</param>
        public void RemoveToken(string tokenNamespace, string name, string value)
        {
            this.ExecuteGeneralAdmin("rm", "token", tokenNamespace, name, value);
        }

        /// <summary>
        /// Sets the user quota.
        /// </summary>
        /// <param name="username">The username to set a quota for.</param>
        /// <param name="resourceName">Name of the resource to set a quota oh.</param>
        /// <param name="value">Quota value.</param>
        public void SetUserQuota(string username, string resourceName, int value)
        {
            this.ExecuteGeneralAdmin("set-quota", "user", username, resourceName, value.ToString());
        }

        /// <summary>
        /// Sets the group quota.
        /// </summary>
        /// <param name="groupName">Name of the group to set a quota for.</param>
        /// <param name="resourceName">Name of the resource to set a quota oh.</param>
        /// <param name="value">Quota value.</param>
        public void SetGroupQuota(string groupName, string resourceName, int value)
        {
            this.ExecuteGeneralAdmin("set-quota", "group", groupName, resourceName, value.ToString());
        }

        /// <summary>
        /// Initiate a periodic rule to vacuum the DB.
        /// </summary>
        /// <param name="dateTime">The time of day to run the first time.</param>
        /// <param name="interval">The minutes value is the time between each subsequent run.</param>
        public void PerformDbVacuum(DateTime dateTime, int interval)
        {
            this.ExecuteGeneralAdmin("pvacuum", string.Format("{0:yyyy-MM-dd.hh:mm:ss}", dateTime), interval.ToString());
        }

        /// <summary>
        /// Calculates the quotas usage.
        /// </summary>
        public void CalculateQuotasUsage()
        {
            this.ExecuteGeneralAdmin("calculate-usage");
        }

        /// <summary>
        /// Removes the unused metadata values.
        /// </summary>
        public void RemoveUnusedMetadata()
        {
            this.ExecuteGeneralAdmin("rm", "unusedAVUs");
        }

        #endregion

        public void AddCollectionMetadata(string name, string attributeName, string attributeValue, string attributeUnits = null)
        {
            var fullName = name;
            if (!name.StartsWith("/"))
            {
                fullName = string.Format("/{0}", name);
            }

            this.ExecuteModifyMetadata("add", "-C", fullName, attributeName, attributeValue, attributeUnits);
        }

        public void ModifyCollectionMetadata(string name, string oldAttributeName, string oldAttributeValue, string newAttributeValue)
        {
            this.ModifyCollectionMetadata(name, oldAttributeName, oldAttributeValue, null, newAttributeValue, null);
        }

        public void ModifyCollectionMetadata(string name, string oldAttributeName, string oldAttributeValue, string newAttributeName, string newAttributeValue)
        {
            this.ModifyCollectionMetadata(name, oldAttributeName, oldAttributeValue, newAttributeName, newAttributeValue, null);
        }

        public void ModifyCollectionMetadata(string name, string oldAttributeName, string oldAttributeValue, string newAttributeName, string newAttributeValue, string newAttributeUnits)
        {
            var fullName = name;
            if (!name.StartsWith("/"))
            {
                fullName = string.Format("/{0}", name);
            }

            var parmeters = new List<string>();
            parmeters.Add("mod");
            parmeters.Add("-C");
            parmeters.Add(fullName);
            parmeters.Add(oldAttributeName);
            parmeters.Add(oldAttributeValue);

            if (newAttributeName != null)
                parmeters.Add(string.Format("n:{0}", newAttributeName));
            if (newAttributeValue != null)
                parmeters.Add(string.Format("v:{0}", newAttributeValue));
            if (newAttributeUnits != null)
                parmeters.Add(string.Format("u:{0}", newAttributeUnits));

            this.ExecuteModifyMetadata(parmeters.ToArray());
        }

        public void RemoveCollectionMetadata(string name, string attributeName, string attributeValue, string attributeUnits = null)
        {
            var fullName = name;
            if (!name.StartsWith("/"))
            {
                fullName = string.Format("/{0}", name);
            }

            this.ExecuteModifyMetadata("rm", "-C", fullName, attributeName, attributeValue, attributeUnits);
        }

        #region Execute API (implemented)

        internal AuthenticationRequestPacket RequestAuthenticationRequest()
        {
            this.SendRequest(RequestCodes.AUTH_REQUEST_AN, null);

            var response = this.ReceiveMessage<AuthenticationRequestPacket>();

            if (response.Exception != null)
            {
                throw response.Exception;
            }

            return response.Body;
        }

        internal void RequestAuthenticationResponse(byte[] challenge)
        {
            if (challenge == null)
                throw new ArgumentNullException("challenge");

            if (this.ConnectionInfo.Password == null)
                throw new ArgumentNullException("password");

            byte[] password;
            if (this.ConnectionInfo.Username.Equals("Anonymous", StringComparison.InvariantCultureIgnoreCase))
            {
                //  Anonymous users has no password
                password = new byte[] { 0 };
            }
            else
            {
                password = Encoding.UTF8.GetBytes(this.ConnectionInfo.Password);
            }

            var bytes = new List<byte>();
            bytes.AddRange(challenge);
            bytes.AddRange(password);
            bytes.AddRange(Enumerable.Repeat((byte)0, _maximumPasswordLength - password.Length));

            var md5Hasher = MD5.Create("MD5");

            // Convert the input string to a byte array and compute the hash.
            byte[] authenticationResponse = md5Hasher.ComputeHash(bytes.ToArray());

            this.SendRequest(RequestCodes.AUTH_RESPONSE_AN, new AuthenticationResponsePacket
            {
                Response = authenticationResponse,
                Username = string.Format("{0}#{1}", this.ConnectionInfo.Username, this.ConnectionInfo.Zone),
            });

            var response = this.ReceiveMessage();

            if (response.ErrorCode == ErrorCodes.CAT_INVALID_AUTHENTICATION)
            {
                throw new IRodsAuthenticationException(string.Format("User '{0}' cannot be authenticated.", this.ConnectionInfo.Username));
            }
            else if (response.ErrorCode == ErrorCodes.CAT_INVALID_USER)
            {
                throw new IRodsAuthenticationException(string.Format("User '{0}' is not valid.", this.ConnectionInfo.Username));
            }
            else if (response.Exception != null)
            {
                throw response.Exception;
            }
        }

        internal ObjectStatus GetObjectStatus(string path)
        {
            var request = new DataObjectInputPacket(path);

            this.SendRequest(RequestCodes.OBJ_STAT_AN, request);

            var response = this.ReceiveMessage<ObjectStatusPacket>();

            if (response.Exception != null)
            {
                throw response.Exception;
            }

            return new ObjectStatus(response.Body, response.ErrorCode);
        }

        internal void ExecuteGeneralAdmin(params string[] arguments)
        {
            var requestArguments = new string[10];

            for (int i = 0; i < arguments.Length; i++)
            {
                requestArguments[i] = arguments[i];
            }

            requestArguments[8] = string.Empty;
            requestArguments[9] = string.Empty;

            this.SendRequest(RequestCodes.GENERAL_ADMIN_AN, new GeneralAdminInputPacket(requestArguments));

            var response = this.ReceiveMessage();

            if (response.ErrorCode == ErrorCodes.SYS_NO_API_PRIV)
            {
                this.SendRequest(RequestCodes.USER_ADMIN_AN, new UserAdminInputPacket(requestArguments));

                response = this.ReceiveMessage();
            }

            if (response.Exception != null)
            {
                throw response.Exception;
            }
        }

        internal void ExecuteModifyMetadata(params string[] arguments)
        {
            var requestArguments = new string[10];

            for (int i = 0; i < arguments.Length; i++)
            {
                requestArguments[i] = arguments[i];
            }

            requestArguments[9] = string.Empty;

            this.SendRequest(RequestCodes.MOD_AVU_METADATA_AN, new MetadataInputPacket(requestArguments));

            var response = this.ReceiveMessage();

            if (response.Exception != null)
            {
                throw response.Exception;
            }
        }

        /// <summary>
        /// Executes the simple query.
        /// </summary>
        /// <param name="form">The form of the output. 1 - without column header; 2 - with column header</param>
        /// <param name="sqlText">The SQL text.</param>
        /// <param name="arguments">The arguments.</param>
        /// <returns></returns>
        internal string ExecuteSimpleQuery(int form, string sqlText, params string[] arguments)
        {
            var maxBufferSize = 1024;

            StringBuilder output = new StringBuilder(maxBufferSize);

            var request = new SimpleQueryInputPacket(form, maxBufferSize, sqlText, arguments);

            var control = 1;

            while (control > 0)
            {
                this.SendRequest(RequestCodes.SIMPLE_QUERY_AN, request);

                var response = this.ReceiveMessage<SimpleQueryOutputPacket>();

                if (response.ErrorCode == ErrorCodes.CAT_NO_ROWS_FOUND)
                {
                    break;
                }
                else if (response.Exception != null)
                {
                    throw response.Exception;
                }
                else if (response.Body == null)
                {
                    throw new IRodsException("Body cannot be null.");
                }

                output.Append(response.Body.Output);

                control = response.Body.Control;
                request.Control = control;
            }

            return output.ToString();
        }

        #endregion

        #region Execute API (not implemented)

        private void ExecuteQuerySpecColl()
        {
            throw new NotImplementedException("ExecuteQuerySpecColl");
        }
        private void ExecuteAuthCheck()
        {
            throw new NotImplementedException("ExecuteAuthCheck");
        }
        private void ExecuteChkNVPathPerm()
        {
            throw new NotImplementedException("ExecuteChkNVPathPerm");
        }
        private void ExecuteChkObjPermAndStat()
        {
            throw new NotImplementedException("ExecuteChkObjPermAndStat");
        }
        private void ExecuteCloseCollection()
        {
            throw new NotImplementedException("ExecuteCloseCollection");
        }
        private void ExecuteCollCreate()
        {
            throw new NotImplementedException("ExecuteCollCreate");
        }
        private void ExecuteCollRepl()
        {
            throw new NotImplementedException("ExecuteCollRepl");
        }
        private void ExecuteDataCopy()
        {
            throw new NotImplementedException("ExecuteDataCopy");
        }
        private void ExecuteDataGet()
        {
            throw new NotImplementedException("ExecuteDataGet");
        }
        private void ExecuteDataObjChksum()
        {
            throw new NotImplementedException("ExecuteDataObjChksum");
        }
        private void ExecuteDataObjClose()
        {
            throw new NotImplementedException("ExecuteDataObjClose");
        }
        private void ExecuteDataObjCopy()
        {
            throw new NotImplementedException("ExecuteDataObjCopy");
        }
        private void ExecuteDataObjCreate()
        {
            throw new NotImplementedException("ExecuteDataObjCreate");
        }
        private void ExecuteDataObjCreateAndStat()
        {
            throw new NotImplementedException("ExecuteDataObjCreateAndStat");
        }
        private void ExecuteDataObjGet()
        {
            throw new NotImplementedException("ExecuteDataObjGet");
        }
        private void ExecuteDataObjLseek()
        {
            throw new NotImplementedException("ExecuteDataObjLseek");
        }
        private void ExecuteDataObjOpen()
        {
            throw new NotImplementedException("ExecuteDataObjOpen");
        }
        private void ExecuteDataObjOpenAndStat()
        {
            throw new NotImplementedException("ExecuteDataObjOpenAndStat");
        }
        private void ExecuteDataObjPhymv()
        {
            throw new NotImplementedException("ExecuteDataObjPhymv");
        }
        private void ExecuteDataObjPut()
        {
            throw new NotImplementedException("ExecuteDataObjPut");
        }
        private void ExecuteDataObjRead()
        {
            throw new NotImplementedException("ExecuteDataObjRead");
        }
        private void ExecuteDataObjRename()
        {
            throw new NotImplementedException("ExecuteDataObjRename");
        }
        private void ExecuteDataObjRepl()
        {
            throw new NotImplementedException("ExecuteDataObjRepl");
        }
        private void ExecuteDataObjRsync()
        {
            throw new NotImplementedException("ExecuteDataObjRsync");
        }
        private void ExecuteDataObjTrim()
        {
            throw new NotImplementedException("ExecuteDataObjTrim");
        }
        private void ExecuteDataObjTruncate()
        {
            throw new NotImplementedException("ExecuteDataObjTruncate");
        }
        private void ExecuteDataObjUnlink()
        {
            throw new NotImplementedException("ExecuteDataObjUnlink");
        }
        private void ExecuteDataObjWrite()
        {
            throw new NotImplementedException("ExecuteDataObjWrite");
        }
        private void ExecuteDataPut()
        {
            throw new NotImplementedException("ExecuteDataPut");
        }
        private void ExecuteExecCmd()
        {
            throw new NotImplementedException("ExecuteExecCmd");
        }
        private void ExecuteExecMyRule()
        {
            throw new NotImplementedException("ExecuteExecMyRule");
        }
        private void ExecuteFileChksum()
        {
            throw new NotImplementedException("ExecuteFileChksum");
        }
        private void ExecuteFileChmod()
        {
            throw new NotImplementedException("ExecuteFileChmod");
        }
        private void ExecuteFileClose()
        {
            throw new NotImplementedException("ExecuteFileClose");
        }
        private void ExecuteFileClosedir()
        {
            throw new NotImplementedException("ExecuteFileClosedir");
        }
        private void ExecuteFileCreate()
        {
            throw new NotImplementedException("ExecuteFileCreate");
        }
        private void ExecuteFileFstat()
        {
            throw new NotImplementedException("ExecuteFileFstat");
        }
        private void ExecuteFileFsync()
        {
            throw new NotImplementedException("ExecuteFileFsync");
        }
        private void ExecuteFileGet()
        {
            throw new NotImplementedException("ExecuteFileGet");
        }
        private void ExecuteFileGetFsFreeSpace()
        {
            throw new NotImplementedException("ExecuteFileGetFsFreeSpace");
        }
        private void ExecuteFileLseek()
        {
            throw new NotImplementedException("ExecuteFileLseek");
        }
        private void ExecuteFileMkdir()
        {
            throw new NotImplementedException("ExecuteFileMkdir");
        }
        private void ExecuteFileOpen()
        {
            throw new NotImplementedException("ExecuteFileOpen");
        }
        private void ExecuteFileOpendir()
        {
            throw new NotImplementedException("ExecuteFileOpendir");
        }
        private void ExecuteFilePut()
        {
            throw new NotImplementedException("ExecuteFilePut");
        }
        private void ExecuteFileRead()
        {
            throw new NotImplementedException("ExecuteFileRead");
        }
        private void ExecuteFileReaddir()
        {
            throw new NotImplementedException("ExecuteFileReaddir");
        }
        private void ExecuteFileRename()
        {
            throw new NotImplementedException("ExecuteFileRename");
        }
        private void ExecuteFileRmdir()
        {
            throw new NotImplementedException("ExecuteFileRmdir");
        }
        private void ExecuteFileStage()
        {
            throw new NotImplementedException("ExecuteFileStage");
        }
        private void ExecuteFileStageToCache()
        {
            throw new NotImplementedException("ExecuteFileStageToCache");
        }
        private void ExecuteFileStat()
        {
            throw new NotImplementedException("ExecuteFileStat");
        }
        private void ExecuteFileSyncToArch()
        {
            throw new NotImplementedException("ExecuteFileSyncToArch");
        }
        private void ExecuteFileTruncate()
        {
            throw new NotImplementedException("ExecuteFileTruncate");
        }
        private void ExecuteFileUnlink()
        {
            throw new NotImplementedException("ExecuteFileUnlink");
        }
        private void ExecuteFileWrite()
        {
            throw new NotImplementedException("ExecuteFileWrite");
        }
        private void ExecuteGeneralRowInsert()
        {
            throw new NotImplementedException("ExecuteGeneralRowInsert");
        }
        private void ExecuteGeneralRowPurge()
        {
            throw new NotImplementedException("ExecuteGeneralRowPurge");
        }
        private void ExecuteGeneralUpdate()
        {
            throw new NotImplementedException("ExecuteGeneralUpdate");
        }
        private void ExecuteGetHostForPut()
        {
            throw new NotImplementedException("ExecuteGetHostForPut");
        }
        private void ExecuteGetMiscSvrInfo()
        {
            throw new NotImplementedException("ExecuteGetMiscSvrInfo");
        }
        private void ExecuteGetRemoteZoneResc()
        {
            throw new NotImplementedException("ExecuteGetRemoteZoneResc");
        }
        private void ExecuteGetRescQuota()
        {
            throw new NotImplementedException("ExecuteGetRescQuota");
        }
        private void ExecuteGetTempPassword()
        {
            throw new NotImplementedException("ExecuteGetTempPassword");
        }
        private void ExecuteGetXmsgTicket()
        {
            throw new NotImplementedException("ExecuteGetXmsgTicket");
        }
        private void ExecuteGsiAuthRequest()
        {
            throw new NotImplementedException("ExecuteGsiAuthRequest");
        }
        private void ExecuteKrbAuthRequest()
        {
            throw new NotImplementedException("ExecuteKrbAuthRequest");
        }
        private void ExecuteL3FileGetSingleBuf()
        {
            throw new NotImplementedException("ExecuteL3FileGetSingleBuf");
        }
        private void ExecuteL3FilePutSingleBuf()
        {
            throw new NotImplementedException("ExecuteL3FilePutSingleBuf");
        }
        private void ExecuteModAccessControl()
        {
            throw new NotImplementedException("ExecuteModAccessControl");
        }
        private void ExecuteModAVUMetadata()
        {
            throw new NotImplementedException("ExecuteModAVUMetadata");
        }
        private void ExecuteModColl()
        {
            throw new NotImplementedException("ExecuteModColl");
        }
        private void ExecuteModDataObjMeta()
        {
            throw new NotImplementedException("ExecuteModDataObjMeta");
        }
        private void ExecuteOpenCollection()
        {
            throw new NotImplementedException("ExecuteOpenCollection");
        }
        private void ExecuteOprComplete()
        {
            throw new NotImplementedException("ExecuteOprComplete");
        }
        private void ExecutePhyBundleColl()
        {
            throw new NotImplementedException("ExecutePhyBundleColl");
        }
        private void ExecutePhyPathReg()
        {
            throw new NotImplementedException("ExecutePhyPathReg");
        }
        private void ExecuteRcvXmsg()
        {
            throw new NotImplementedException("ExecuteRcvXmsg");
        }
        private void ExecuteReadCollection()
        {
            throw new NotImplementedException("ExecuteReadCollection");
        }
        private void ExecuteRegColl()
        {
            throw new NotImplementedException("ExecuteRegColl");
        }
        private void ExecuteRegDataObj()
        {
            throw new NotImplementedException("ExecuteRegDataObj");
        }
        private void ExecuteRegReplica()
        {
            throw new NotImplementedException("ExecuteRegReplica");
        }
        private void ExecuteRmColl()
        {
            throw new NotImplementedException("ExecuteRmColl");
        }
        private void ExecuteRmCollOld()
        {
            throw new NotImplementedException("ExecuteRmCollOld");
        }
        private void ExecuteRuleExecDel()
        {
            throw new NotImplementedException("ExecuteRuleExecDel");
        }
        private void ExecuteRuleExecMod()
        {
            throw new NotImplementedException("ExecuteRuleExecMod");
        }
        private void ExecuteRuleExecSubmit()
        {
            throw new NotImplementedException("ExecuteRuleExecSubmit");
        }
        private void ExecuteSendXmsg()
        {
            throw new NotImplementedException("ExecuteSendXmsg");
        }
        private void ExecuteStructFileBundle()
        {
            throw new NotImplementedException("ExecuteStructFileBundle");
        }
        private void ExecuteStructFileExtAndReg()
        {
            throw new NotImplementedException("ExecuteStructFileExtAndReg");
        }
        private void ExecuteStructFileExtract()
        {
            throw new NotImplementedException("ExecuteStructFileExtract");
        }
        private void ExecuteStructFileSync()
        {
            throw new NotImplementedException("ExecuteStructFileSync");
        }
        private void ExecuteSubStructFileClose()
        {
            throw new NotImplementedException("ExecuteSubStructFileClose");
        }
        private void ExecuteSubStructFileClosedir()
        {
            throw new NotImplementedException("ExecuteSubStructFileClosedir");
        }
        private void ExecuteSubStructFileCreate()
        {
            throw new NotImplementedException("ExecuteSubStructFileCreate");
        }
        private void ExecuteSubStructFileFstat()
        {
            throw new NotImplementedException("ExecuteSubStructFileFstat");
        }
        private void ExecuteSubStructFileGet()
        {
            throw new NotImplementedException("ExecuteSubStructFileGet");
        }
        private void ExecuteSubStructFileLseek()
        {
            throw new NotImplementedException("ExecuteSubStructFileLseek");
        }
        private void ExecuteSubStructFileMkdir()
        {
            throw new NotImplementedException("ExecuteSubStructFileMkdir");
        }
        private void ExecuteSubStructFileOpen()
        {
            throw new NotImplementedException("ExecuteSubStructFileOpen");
        }
        private void ExecuteSubStructFileOpendir()
        {
            throw new NotImplementedException("ExecuteSubStructFileOpendir");
        }
        private void ExecuteSubStructFilePut()
        {
            throw new NotImplementedException("ExecuteSubStructFilePut");
        }
        private void ExecuteSubStructFileRead()
        {
            throw new NotImplementedException("ExecuteSubStructFileRead");
        }
        private void ExecuteSubStructFileReaddir()
        {
            throw new NotImplementedException("ExecuteSubStructFileReaddir");
        }
        private void ExecuteSubStructFileRename()
        {
            throw new NotImplementedException("ExecuteSubStructFileRename");
        }
        private void ExecuteSubStructFileRmdir()
        {
            throw new NotImplementedException("ExecuteSubStructFileRmdir");
        }
        private void ExecuteSubStructFileStat()
        {
            throw new NotImplementedException("ExecuteSubStructFileStat");
        }
        private void ExecuteSubStructFileTruncate()
        {
            throw new NotImplementedException("ExecuteSubStructFileTruncate");
        }
        private void ExecuteSubStructFileUnlink()
        {
            throw new NotImplementedException("ExecuteSubStructFileUnlink");
        }
        private void ExecuteSubStructFileWrite()
        {
            throw new NotImplementedException("ExecuteSubStructFileWrite");
        }
        private void ExecuteSyncMountedColl()
        {
            throw new NotImplementedException("ExecuteSyncMountedColl");
        }
        private void ExecuteUnbunAndRegPhyBunfile()
        {
            throw new NotImplementedException("ExecuteUnbunAndRegPhyBunfile");
        }
        private void ExecuteUnregDataObj()
        {
            throw new NotImplementedException("ExecuteUnregDataObj");
        }
        private void ExecuteUserAdmin()
        {
            throw new NotImplementedException("ExecuteUserAdmin");
        }

        #endregion

        internal void SendRequest(RequestCodes request, Packet body, Stream data)
        {
            var message = new Message(MessageTypes.Request, request, body, data);

            //  Send request message
            this.SendMessage(message);
        }

        internal void SendRequest(RequestCodes request, Packet body)
        {
            var message = new Message(MessageTypes.Request, request, body);

            //  Send request message
            this.SendMessage(message);
        }

        internal void SendRequest(RequestCodes request, int value)
        {
            var message = new Message(MessageTypes.Request, request, new IntegerPacket(value));

            //  Send request message
            this.SendMessage(message);
        }

        internal Message<Packet> ReceiveMessage()
        {
            return this.ReceiveMessage<Packet>();
        }

        internal Message<T> ReceiveMessage<T>() where T : Packet
        {
            //  Read header length
            var headerLengthData = this.Read(4);
            var headerLength = (headerLengthData[0] << 24 | headerLengthData[1] << 16 | headerLengthData[2] << 8 | headerLengthData[3]);

            //  Read message header
            var headerData = this.Read(headerLength);
            var header = this.ReadPacket(headerData, PackingType.Xml) as MessageHeaderPacket;

            T body = default(T);
            ErrorPacket error = null;

            //  Read main message
            byte[] bodyData = null;
            if (header.MessageLength > 0)
            {
                bodyData = this.Read(header.MessageLength);
                //  Load message body
                body = this.ReadPacket(bodyData) as T;
            }

            byte[] errorData = null;
            if (header.ErrorLength > 0)
            {
                errorData = this.Read(header.ErrorLength);
                error = this.ReadPacket(errorData) as ErrorPacket;
            }

            //  TODO:   Dispose of this stream
            Stream binaryData = null;
            if (header.BinaryStringLength > 0)
                binaryData = new MemoryStream(this.Read(header.BinaryStringLength));

            if (bodyData != null)
                Debug.WriteLine(Encoding.ASCII.GetString(bodyData));
            else
                Debug.WriteLine("No body");

            //  TODO:   Populate binary stream
            return new Message<T>(header, body, error, binaryData);
        }

        internal string ParsePath(string path)
        {
            return this.ParsePath(path, this.WorkingDirectory);
        }

        internal string ParsePath(string path, string relativeDirectory)
        {
            string rodsPath = string.Empty;

            //  Resolve to current working directory path
            if (string.IsNullOrEmpty(path) || path.Equals(".") || path.Equals("./"))
            {
                rodsPath = this.WorkingDirectory;
            }
            else if (path.Equals("~") || path.Equals("~/") || path.Equals("^") || path.Equals("^/"))
            {
                rodsPath = this.HomeDirectory;
            }
            else if (path.StartsWith("~") || path.StartsWith("^"))
            {
                if (path[1] == '/')
                {
                    rodsPath = string.Format("{0}/{1}", this.HomeDirectory, path.Substring(2));
                }
                else
                {
                    rodsPath = string.Format("{0}/{1}", relativeDirectory, path.Substring(2));
                }
            }
            else if (path.StartsWith("/"))
            {
                rodsPath = path;
            }
            else
            {
                rodsPath = string.Format("{0}/{1}", relativeDirectory, path);
            }


            if (string.IsNullOrEmpty(rodsPath))
                throw new InvalidOperationException("Resolved rods path cannot be empty");

            //  TODO:   take out any "//"
            //  TODO:   take out any "/./"
            //  TODO:   take out any /../
            //  TODO:   handle "/.", "/.." and "/" at the end
            //  TODO:   Handle special case blank path to "/"
            //  TODO:   take out "/."

            return rodsPath;
        }

        private string EncryptPassword(string value)
        {
            var MAX_PASSWORD_LEN = 50;

            var passwordValue = value;
            var length = MAX_PASSWORD_LEN - 10 - value.Length;

            if (length > 15)
            {
                passwordValue = string.Concat(value, "1gCBizHWbwIYyWLoysGzTe6SyzqFKMniZX05faZHWAwQKXf6Fs".Substring(0, length));
            }

            var hashKey = new StringBuilder(100);
            hashKey.Append(this.ConnectionInfo.Password);
            //  Take first 16 bytes of session challenge
            for (int i = 0; i < 16; i++)
            {
                hashKey.AppendFormat("{0:x2}", this._sessionChallenge[i]);
            }

            var firstChar = (65 + (DateTime.Now.Millisecond & 0x1f));
            //var firstChar = 65;
            var input = string.Format("{0}.ObfV2{1}", (char)firstChar, passwordValue);

            StringBuilder key = new StringBuilder();
            using (var md5 = MD5.Create())
            {
                var result = md5.ComputeHash(Encoding.ASCII.GetBytes(hashKey.ToString().PadRight(100, '\0')));

                foreach (var k in result)
                {
                    key.AppendFormat("{0:x2}", k);
                }
            }

            //  Array of characters that we will transpose
            var wheel = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!\"#$%&'()*+,-./";

            var buffer = new byte[65];

            //  Get the MD5 digest of the key to get some bytes with many different values
            using (var md5 = MD5.Create())
            {
                var part1 = md5.ComputeHash(Encoding.ASCII.GetBytes(key.ToString().PadRight(100, '\0')));
                Array.Copy(part1, buffer, 16);

                var part2 = md5.ComputeHash(buffer, 0, 16);
                Array.Copy(part2, 0, buffer, 16, 16);

                var part3 = md5.ComputeHash(buffer, 0, 32);
                Array.Copy(part3, 0, buffer, 32, 16);
                Array.Copy(part3, 0, buffer, 48, 16);
            }

            var output = new StringBuilder();
            var keyIndex = 0;
            int pc = 0; // previous character 

            foreach (var c in input)
            {
                var k = buffer[keyIndex++];

                if (keyIndex > 60)
                    keyIndex = 0;

                var charIndex = wheel.IndexOf(c);

                if (charIndex > -1)
                {
                    var j = (charIndex + k + pc) % wheel.Length;

                    output.Append(wheel[j]);

                    pc = output[output.Length - 1] & 0xff;
                }
                else
                {
                    output.Append(c);
                }
            }

            return output.ToString();
        }

        private void SendMessage(Message message)
        {
            var data = message.GetBytes(this._packingType);
            Debug.WriteLine(Encoding.ASCII.GetString(data));
            this.Write(data);
        }

        private Packet ReadPacket(byte[] packetData)
        {
            return this.ReadPacket(packetData, this._packingType);
        }

        private Packet ReadPacket(byte[] packetData, PackingType packingType)
        {
            Packet packet = null;
            switch (packingType)
            {
                case PackingType.Native:
                    throw new NotImplementedException();
                case PackingType.Xml:
                    using (var ms = new MemoryStream(packetData))
                    {
                        using (var reader = XmlReader.Create(ms))
                        {
                            var xml = XElement.Load(reader);

                            packet = Packet.Load(xml);
                        }
                    }

                    break;
                default:
                    break;
            }
            return packet;
        }

        /// <summary>
        /// Reads the specified length of bytes from the server
        /// </summary>
        /// <param name="length">The length.</param>
        /// <returns></returns>
        private byte[] Read(int length)
        {
            var buffer = new byte[length];
            var offset = 0;
            int receivedTotal = 0;  // how many bytes is already received
            do
            {
                try
                {
                    var receivedBytes = this._socket.Receive(buffer, offset + receivedTotal, length - receivedTotal, SocketFlags.None);
                    if (receivedBytes > 0)
                    {
                        receivedTotal += receivedBytes;
                        continue;
                    }
                    else
                    {
                        throw new IRodsException("An established connection was aborted by the software in your host machine.");
                    }
                }
                catch (SocketException exp)
                {
                    if (exp.SocketErrorCode == SocketError.WouldBlock ||
                        exp.SocketErrorCode == SocketError.IOPending ||
                        exp.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
                    {
                        // socket buffer is probably empty, wait and try again
                        Thread.Sleep(30);
                    }
                    else
                        throw;  // any serious error occurred
                }
            } while (receivedTotal < length);


            return buffer;
        }

        /// <summary>
        /// Writes the specified data to the server.
        /// </summary>
        /// <param name="data">The data.</param>
        private void Write(byte[] data)
        {
            int sent = 0;  // how many bytes is already sent
            int length = data.Length;

            do
            {
                try
                {
                    sent += this._socket.Send(data, sent, length - sent, SocketFlags.None);
                }
                catch (SocketException ex)
                {
                    if (ex.SocketErrorCode == SocketError.WouldBlock ||
                        ex.SocketErrorCode == SocketError.IOPending ||
                        ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
                    {
                        // socket buffer is probably full, wait and try again
                        Thread.Sleep(30);
                    }
                    else
                        throw;  // any serious error occurred
                }
            } while (sent < length);

        }

        #region IDisposable Members

        private bool _disposed = false;

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged ResourceMessages.
        /// </summary>
        public void Dispose()
        {
            this.Close();
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged ResourceMessages.</param>
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!this._disposed)
            {
                // If disposing equals true, dispose all managed
                // and unmanaged ResourceMessages.
                if (disposing)
                {
                    if (this._socket != null)
                    {
                        //  Send close request
                        this.SendMessage(new Message(MessageTypes.Disconnect));

                        this._socket.Close();
                        this._socket = null;
                    }

                    this.IsConnected = false;
                }

                // Note disposing has been done.
                this._disposed = true;
            }
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="IRodsConnection"/> is reclaimed by garbage collection.
        /// </summary>
        ~IRodsConnection()
        {
            // Do not re-create Dispose clean-up code here.
            // Calling Dispose(false) is optimal in terms of
            // readability and maintainability.
            Dispose(false);
        }

        #endregion
    }
}
